/*
 * CMediaPlayer.cpp
 *
 *  Created on: 2015年6月1日
 *      Author: chuanjiang.zh@qq.com
 */

#include "CMediaPlayer.h"
#include <memory>
#include <errno.h>
#include "TFileUtil.h"
#include "CLog.h"
#include "TStringUtil.h"


class CPlayerContext
{
public:
    CPlayerContext()
    {
        av_register_all();

        SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER);
    }

    ~CPlayerContext()
    {
        SDL_Quit();
    }
};


///////////////////////////////////////////////////////////////////
static std::auto_ptr< CPlayerContext >  s_playerContext;

///////////////////////////////////////////////////////////////////





CMediaPlayer::CMediaPlayer():
    m_videoCodecCtx(),
    m_parserCtx(),
    m_videoCodecID(AV_CODEC_ID_H264),
    m_audioCodecCtx(),
    m_audioCodecID(AV_CODEC_ID_PCM_ALAW),
    m_channels(1),
    m_sampleRate(8000),
    m_audioDev(),
    m_audioSpec(),
    m_curFrame(),
    m_framePos(),
    m_state(),
    m_swrContext()
{
    m_hwnd = NULL;

    m_videoPktCount = 0;
    m_audioPktCount = 0;

    if (!s_playerContext.get())
    {
        s_playerContext.reset(new CPlayerContext());
    }
}

CMediaPlayer::~CMediaPlayer()
{
    close();
}

int CMediaPlayer::open(int vcodec, int acodec, int samplerate, int channels)
{
    if (isOpen())
    {
        return 0;
    }

    m_videoCodecID = (AVCodecID)vcodec;
    m_audioCodecID = (AVCodecID)acodec;
    m_sampleRate = samplerate;
    m_channels = channels;

    CLog::debug("CMediaPlayer::open. rate:%d, chl:%d\n", samplerate, channels);

    int ret = openVideoCodec(m_videoCodecID);
    openAudioCodec(m_audioCodecID);

    openAudioDev();

    m_state = kPlaying;

    start();

    return ret;
}

void CMediaPlayer::close()
{
    if (isRunning())
    {
        stop();
    }

    m_state = kStopped;

    closeVideoCodec();
    closeAudioCodec();

    closeAudioDev();

    m_frameQueue.clear();
    clearFrame();

    m_pktQueue.clear();

    m_videoPktCount = 0;
    m_audioPktCount = 0;

    if (m_swrContext)
    {
        swr_close(m_swrContext);
        swr_free(&m_swrContext);
        m_swrContext = NULL;
    }
}

bool CMediaPlayer::isOpen()
{
    return (m_videoCodecCtx != NULL) || (m_audioCodecCtx != NULL);
}


void CMediaPlayer::setVideoWnd(HWND hwnd)
{
    m_hwnd = hwnd;
}

int CMediaPlayer::input(const MediaPacket& pkt)
{
    if (!isOpen())
    {
        return ENODEV;
    }

    if (m_state != kPlaying)
    {
        return EACCES;
    }

    if (pkt.type == MEDIA_TYPE_VIDEO)
    {
        uint8_t* pOutData = NULL;
        int outSize = 0;

        uint8_t* pInData = pkt.data;
        int inLen = pkt.size;

        while (inLen > 0)
        {
            int len = av_parser_parse2(m_parserCtx, m_videoCodecCtx, &pOutData, &outSize,
                pInData, inLen, pkt.ts, pkt.ts, 0);

            pInData += len;
            inLen -= len;

            if (outSize > 0)
            {
                m_pktQueue.push(AVPacketQueue::VIDEO_INDEX, pOutData, outSize, pkt.ts);
                m_event.post();
            }
        }

        m_videoPktCount ++;
    }
    else
    {
        m_audioPktCount ++;

        m_pktQueue.push(AVPacketQueue::AUDIO_INDEX, pkt.data, pkt.size, pkt.ts);
        m_event.post();
    }

    return 0;
}


int CMediaPlayer::openVideoCodec(AVCodecID id)
{
    m_parserCtx = av_parser_init(id);

    AVCodec* pCodec = avcodec_find_decoder(id);
    m_videoCodecCtx = avcodec_alloc_context3(pCodec);
    m_videoCodecCtx->refcounted_frames=1;

    int ret = avcodec_open2(m_videoCodecCtx, pCodec, NULL);
    if (ret < 0)
    {
        avcodec_close(m_videoCodecCtx);
        av_free(m_videoCodecCtx);
        m_videoCodecCtx = NULL;
        return EBADF;
    }
    return ret;
}

void CMediaPlayer::closeVideoCodec()
{
    if (m_parserCtx)
    {
        av_parser_close(m_parserCtx);
        m_parserCtx = NULL;
    }

    if (m_videoCodecCtx)
    {
        avcodec_close(m_videoCodecCtx);
        av_free(m_videoCodecCtx);
        m_videoCodecCtx = NULL;
    }
}

void CMediaPlayer::inputVideo(AVPacket& pkt)
{
    AVFrame* frame = av_frame_alloc();
    int gotPicture = 0;
    int err = avcodec_decode_video2(m_videoCodecCtx, frame, &gotPicture, &pkt);
    if (gotPicture)
    {
        renderVideo(frame);
    }
    else
    {
        av_frame_free(&frame);
    }
}

void CMediaPlayer::renderVideo(AVFrame* frame)
{
    std::string osd = comn::StringUtil::format("v=%d, a=%d",
            m_videoPktCount, m_audioPktCount);
    m_render.setOsd(osd);

    m_render.draw(m_hwnd, frame, -1, -1);
    
    av_frame_free(&frame);
}

int CMediaPlayer::openAudioCodec(AVCodecID id)
{
    AVCodec* pCodec = avcodec_find_decoder(id);
    if (!pCodec)
    {
        return EBADF;
    }

    int ret = EBADF;
    m_audioCodecCtx = avcodec_alloc_context3(pCodec);

    m_audioCodecCtx->refcounted_frames=1;
    m_audioCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO;
    m_audioCodecCtx->channels = m_channels;
    m_audioCodecCtx->sample_rate = m_sampleRate;
    m_audioCodecCtx->bits_per_raw_sample = 16;//m_sampleBits;

    if (avcodec_open2(m_audioCodecCtx, pCodec, NULL) < 0)
    {
        avcodec_close(m_audioCodecCtx);
        av_free(m_audioCodecCtx);
        m_audioCodecCtx = NULL;
        return EBADF;
    }

    return 0;
}

void CMediaPlayer::closeAudioCodec()
{
    if (!m_audioCodecCtx)
    {
        return;
    }

    avcodec_close(m_audioCodecCtx);
    av_free(m_audioCodecCtx);
    m_audioCodecCtx = NULL;
}

void CMediaPlayer::inputAudio(AVPacket& pkt)
{
    if (!m_audioCodecCtx)
    {
        return;
    }

    int ret = -1;
    AVPacket pktIn = pkt;
    while (pktIn.size > 0)
    {
        AVFrame* pFrame = av_frame_alloc();
        int gotFrame = 0;
        int bytes = avcodec_decode_audio4(m_audioCodecCtx, pFrame, &gotFrame, &pktIn);
        if (bytes <= 0)
        {
            av_frame_free(&pFrame);
            break;
        }

        pktIn.size -= bytes;
        pktIn.data += bytes;

        if (gotFrame)
        {
            renderAudio(pFrame);
        }
        else
        {
            av_frame_free(&pFrame);
        }
    }
}

void CMediaPlayer::renderAudio(AVFrame* frame)
{
    if (m_state == kPlaying)
    {
        if (m_audioCodecCtx->sample_fmt != AV_SAMPLE_FMT_S16)
        {
            AVFrame* outFrame = av_frame_alloc();
            outFrame->format = AV_SAMPLE_FMT_S16;
            outFrame->channels = m_audioCodecCtx->channels;
            outFrame->channel_layout = m_audioCodecCtx->channel_layout;
            outFrame->sample_rate = m_audioCodecCtx->sample_rate;
            outFrame->pts = frame->pts;

            resample(frame, outFrame);

            m_frameQueue.push(outFrame);

            av_frame_free(&frame);
        }
        else
        {
            m_frameQueue.push(frame);
        }
    }
    else
    {
        av_frame_free(&frame);
    }
}


void CMediaPlayer::sdlAudioCallback(void* userdata, Uint8* stream, int len)
{
    CMediaPlayer* pRender = (CMediaPlayer*)userdata;
    pRender->audioCallback(stream, len);
}

void CMediaPlayer::audioCallback(Uint8* stream, int len)
{
    if (!isAudioDevOpen())
    {
        return;
    }

    memset(stream, m_audioSpec.silence, len);

    int vol = SDL_MIX_MAXVOLUME;

    Uint8* buffer = stream;
    int size = len;

    int written = 0;
    // mix last frame
    written = mixLastFrame(buffer, size, vol);
    buffer += written;
    size -= written;

    while ((size > 0) && (m_state == kPlaying))
    {
        AVFrame* frame = m_frameQueue.pop();
        if (!frame)
        {
            break;
        }

        bool consumed = false;
        written = mixFrame(frame, buffer, size, vol, consumed);

        buffer += written;
        size -= written;

        if (consumed)
        {
            av_frame_free(&frame);
        }
        else
        {
            saveFrame(frame, written);
            break;
        }
    }

}


int CMediaPlayer::mixFrame(AVFrame* pFrame, Uint8* buffer, int size, int vol, bool& consumed)
{
    if (size == 0)
    {
        return 0;
    }

    return mixFrame(pFrame, 0, buffer, size, vol, consumed);
}

int CMediaPlayer::mixFrame(AVFrame* pFrame, int pos, Uint8* buffer, int size, int vol, bool& consumed)
{
    if (!pFrame)
    {
        return 0;
    }

    int frameSize = pFrame->linesize[0];
    int bytes = pFrame->channels * pFrame->nb_samples * 2;
    if (frameSize != bytes)
    {
        //OutputDebugStringA("pFrame->linesize[0] != bytes\n");
    }

    frameSize = bytes;

    if (pos >= frameSize)
    {
        consumed = true;
        return 0;
    }

    uint8_t* frameData = pFrame->data[0];
    int length = std::min(frameSize - pos, size);
    SDL_MixAudioFormat(buffer, frameData + pos, AUDIO_S16SYS, length, vol);

    consumed = ((frameSize - pos) == length);
    return length;
}

int CMediaPlayer::mixLastFrame(Uint8* buffer, int size, int vol)
{
    comn::AutoCritSec lock(m_cs);
    if (!m_curFrame)
    {
        return 0;
    }

    bool consumed = false;
    int written = mixFrame(m_curFrame, m_framePos, buffer, size, vol, consumed);
    if (consumed)
    {
        clearFrame();
    }
    else
    {
        m_framePos += written;
    }
    return written;
}

void CMediaPlayer::saveFrame(AVFrame* pFrame, int pos)
{
    clearFrame();

    comn::AutoCritSec lock(m_cs);
    m_curFrame = pFrame;
    m_framePos = pos;
}

void CMediaPlayer::clearFrame()
{
    comn::AutoCritSec lock(m_cs);
    if (m_curFrame)
    {
        av_frame_free(&m_curFrame);
        m_framePos = 0;
    }
}



bool CMediaPlayer::openAudioDev()
{
    comn::AutoCritSec lock(m_cs);

    int frameSize = 1152;

    SDL_AudioSpec want;
    SDL_zero(want);
    want.freq = m_sampleRate;
    want.format = AUDIO_S16SYS;
    want.channels = m_channels;
    want.samples = frameSize;
    want.callback = sdlAudioCallback;
    want.userdata = this;
    want.channels = m_channels;
    want.freq = m_sampleRate;

    m_audioDev = SDL_OpenAudioDevice(NULL, 0, &want, &m_audioSpec, 0);
    if (m_audioDev == 0)
    {
        CoInitialize(NULL);
        m_audioDev = SDL_OpenAudioDevice(NULL, 0, &want, &m_audioSpec, 0);
        if (m_audioDev == 0)
        {
            const char* err = SDL_GetError();
            OutputDebugStringA(err);
        }
    }

    SDL_PauseAudioDevice(m_audioDev, FALSE);

    return isAudioDevOpen();
}

void CMediaPlayer::closeAudioDev()
{
    if (!isAudioDevOpen())
    {
        return;
    }

    comn::AutoCritSec lock(m_cs);

    SDL_PauseAudioDevice(m_audioDev, TRUE);

    SDL_CloseAudioDevice(m_audioDev);
    m_audioDev = 0;
}

bool CMediaPlayer::isAudioDevOpen()
{
    return (m_audioDev != 0);
}

int CMediaPlayer::run()
{
    while (!m_canExit)
    {
        AVPacketPtr pkt = m_pktQueue.pop();
        if (!pkt)
        {
            m_event.timedwait(100);
            continue;
        }
        
        handle(pkt);
    }
    return 0;
}

void CMediaPlayer::doStop()
{
    m_event.post();
}

void CMediaPlayer::handle(AVPacketPtr& pkt)
{
    if (pkt->stream_index == AVPacketQueue::VIDEO_INDEX)
    {
        inputVideo(*pkt);
    }
    else
    {
        inputAudio(*pkt);
    }
}

int CMediaPlayer::resample(AVFrame* inFrame, AVFrame* outFrame)
{
    if (m_swrContext == NULL)
    {
        m_swrContext = swr_alloc();

        swr_config_frame(m_swrContext, outFrame, inFrame);

        swr_init(m_swrContext);
    }
    else
    {
        int ret = swr_config_frame(m_swrContext, outFrame, inFrame);
    }

    int ret = swr_convert_frame(m_swrContext, outFrame, inFrame);

    return outFrame->nb_samples;
}